classdef instance_opt    < handle
    properties
        samplingTime = 60;
        m_utilization, m_servers, m_num_users;
        price_per_vm_hour;
        vm_instantiation_time;
        t=1;  type=2; n=3;         
        inst_types = [];
        inst_units = [];
        inst_duration = [];
        inst_price = []; 
        dt=15% min
        column=struct('month', 1 ,'day', 2,'hour', 3,'minute', 4,'reqnum', 5);
        % a-star data
        total_set = state.empty;        
        is_closed=[];
        g_score=[]; h_score=[]; f_score=[];        
        pu;
    end
    
    methods
        function obj=instance_opt
            obj.samplingTime = 60;
            obj.price_per_vm_hour=0.08; %dollars
            obj.vm_instantiation_time=8;  %minutes
            % load data
            % pu = [10 40 70 20 70 50 60 80 80 60 30];
            % pu= sin([1:300]/10)*100+400;
                m= draw_result().load_data('data/day42_per_min.txt');        
                between = (1 : 23 *60);
               obj.pu = (14*m(between ,obj.column.reqnum)/max(m(: ,obj.column.reqnum)))';
        end

        function initialize_with_dummy_prices(obj)
            obj.inst_types = [1; 2; 3; 4];
            obj.inst_units = [4;1; 2; 10];
            obj.inst_duration = [60; 60; 60; 60];
            obj.inst_price = [9; 2; 3; 10]; 
        end
        
        function initialize_with_amzn_prices(obj)
            units=prices_amz().get_compute_units;
            obj.inst_types = (1:2*size(units,1)); % yearly and hourly rates
            % adding yearly instances
            obj.inst_units = units;
            obj.inst_price = prices_amz().get_price_per_year;
            obj.inst_duration = ones(size(units,1),1)* 24*60*365;
            
            % adding hourly instances            
            obj.inst_units =[obj.inst_units  ;   units];            
            obj.inst_price =[obj.inst_price ;   prices_amz().get_price_per_hour];
            obj.inst_duration =[obj.inst_duration ;  ones(size(units,1),1)* 60];            
        end
        
        function cost_=cost(obj,servers_)
            hours=(sum(obj.per_hour_cost_filter(servers_))/60);
            cost_ = hours * obj.price_per_vm_hour;
        end
             
        function d=indexof(obj,t)
            d=t+1;
        end
        
        function s=cumsumfunc(obj,m)
            if not(isempty(m))
                s=zeros(1,max(m(:,obj.t)+obj.inst_duration(m(:,obj.type))) );                 
                for i=1:size(m,1)
                    m_duration = obj.inst_duration(m(i,obj.type));
                    s(1,obj.indexof(m(i,obj.t)) : obj.indexof(m(i,obj.t)+m_duration)-1)=...
                        s(1, obj.indexof(m(i,obj.t)):obj.indexof(m(i,obj.t)+m_duration)-1)+obj.inst_units(m(i,obj.type))*m(i,obj.n);
                end
            else
                s=[0];
            end
        end
        
        function is=covers(obj,cumsum,pu)
             if size(cumsum,2)>size(pu,2) 
                pu =[pu   zeros(1,size(cumsum,2)-size(pu,2))];
             elseif size(cumsum,2)<size(pu,2) 
                cumsum =[cumsum  zeros(1,size(pu,2)-size(cumsum,2))];
             end
            is=isempty(find(cumsum-pu<0,1));
        end       
        
        function grand_tot_cost = heuristic_cost_estimate(obj, state, pu)
            mm=state.m;
             cumsum= obj.cumsumfunc(mm);
             if size(cumsum,2)>size(pu,2) 
                 pu =[pu   zeros(1,size(cumsum,2)-size(pu,2))];
%                  err = MException('cumsum:more','error: size(cumsum,2)>size(pu,2)');
%                  pu
%                  cumsum
%                  throw(err);
             elseif size(cumsum,2)<size(pu,2) 
                cumsum =[cumsum   zeros(1,size(pu,2)-size(cumsum,2))];
             end
            
            total_left_unit_minutes= pu -cumsum;
            total_left_unit_minutes(find(total_left_unit_minutes<0))=0;                   
            average_price_per_unit_and_hour =  mean(obj.inst_price ./ obj.inst_units);          % heuristic
            grand_tot_cost=sum(total_left_unit_minutes/60) * average_price_per_unit_and_hour;
        end

        function grand_tot_cost=compute_g_score(obj,state)
            mm=state.m;
            grand_tot_cost=0;
            for k=1:size(mm,1)
                grand_tot_cost = grand_tot_cost + mm(k,obj.n) * obj.inst_price(mm(k,obj.type));
            end            
        end
        
        function test(obj)
                m= draw_result().load_data('data/day42_per_min.txt');        
                g=struct('month', 1 ,'day', 2,'hour', 3,'minute', 4,'reqnum', 5)
                u=UtilityLib();
                between = (1 : 23 *60);
                pu = 14*m(: ,g.reqnum)/max(m(: ,g.reqnum));
                plot(pu);                
                title('Number of users over time');
                xlabel('Time(minutes)');
                ylabel('Number of users');

        end

        function s_prim=move_time(obj,s,dt)
            s_prim=s; 
            s_prim.t = s_prim.t + dt;
        end

         function s_prim=add_instance(obj,s, type) 
              if isempty(s.m)
                    s_prim=state;  copyobj(s,s_prim);                    
                    s_prim.m=vertcat(s.m, [ s.t, type 1]);
              else 
                    [i j] = find(ismember(s.m(:, [obj.t,obj.type]), [s.t type], 'rows')>0);     %find the matching row
                    if not(isempty(i))
                         s_prim=state;  copyobj(s,s_prim);      %hopefully it clones
                        s_prim.m(i(1),obj.n)= s.m(i(1),obj.n) +1;
                    else
                         s_prim=state;  copyobj(s,s_prim);           
                        s_prim.m=vertcat(s.m, [ s.t, type 1]);
                    end
             end        
            s_prim.m=sortrows(s_prim.m,[ obj.t  obj.type]);
         end
        
        function neighbor_set=each_neighbor(obj,s)                
            % add_instance
            neighbor_set=[];
            for type_=1:size(obj.inst_types,1)
                s_prim=obj.add_instance(s, type_) ;
                neighbor_set =[neighbor_set s_prim]; 
            end
            % move time
            
            s_prim=obj.move_time(s,obj.dt);
            neighbor_set =[neighbor_set s_prim];             
        end        
        
        function index=add_to_openset(obj,s)
            index=size(obj.total_set,2)+1;
            obj.total_set(index)=s;
            obj.is_closed(index)=0; 
        end

        function transfer_to_closedset(obj, x_index)
            obj.is_closed(x_index)= 1;
        end
        
        function [x_index x min_score]=lowest_f_score_from_openset(obj)            
%             [j]=find(f_score==min(f_score));
%                 x=openset(j);
                min_score=Inf;
                x=[];
                for k=1:size(obj.total_set,2)
                    if (obj.f_score(k)<min_score && obj.is_closed(k)==0)
                        x_index=k;
                        x= obj.total_set(k);
                        min_score=obj.f_score(k);
                    end
                end
        end

        % a=instance_opt();a.all; t=a.total_set;
        % [x score]=instance_opt().find_opt();
        function [x score]=find_opt(obj)
           
            %   closedset = [];        %the empty set    // The set of nodes already evaluated.
             start_index = 1;
             start=state([],0);
             obj.add_to_openset(start); % {start}    // The set of tentative nodes to be evaluated, initially containing the start node
             came_from = []; %the empty map    // The map of navigated nodes.
            x=[]; score=Inf;
 
             obj.g_score(start_index) = 0;    % Cost from start along best known path.
             obj.h_score(start_index) = obj.heuristic_cost_estimate(start, obj.pu);
             obj.f_score(start_index) = obj.g_score(start_index) + obj.h_score(start_index);    % Estimated total cost from start to goal through y.

            % grand_tot_cost=Inf;
            for i=1:100      % while openset is not empty
                % x := the node in openset having the lowest f_score[] value
                [x_index x score]=obj.lowest_f_score_from_openset();
                disp(strcat('lowest f_score belongs to ',num2str(x_index),' with score ',num2str(score)));
             %   x.m
                
                % if x = goal   return reconstruct_path(came_from, came_from[goal])
                if obj.covers(obj.cumsumfunc(x.m),obj.pu) % curve is satisfied                    
                end
             
                % remove x from openset and add to closed set
                obj.transfer_to_closedset(x_index);
                
                % foreach y in neighbor_nodes(x)
                 neighbor_set=obj.each_neighbor(x);
                 for y = neighbor_set
                        % we are sure that its in open set since changes are incremental % if y in closedset continue
                        % tentative_g_score := g_score[x] + dist_between(x,y)               
                        % if y not in openset
                             % add y to openset
                              y_index=obj.add_to_openset(y); 
                             % tentative_is_better := true
                         obj.g_score(y_index) = obj.compute_g_score(y); 
                         obj.h_score(y_index) = obj.heuristic_cost_estimate(y,obj.pu);                               
                         obj.f_score(y_index) = obj.g_score(y_index) + obj.h_score(y_index); 
                 end % neigbor set nav
            end % open set nav
            
           
        end %all
        
        % obj.visualize(x)
        function visualize(obj, x)            
            % obj.cumsumfunc(m)
%            res=[mm(:,1) mm(:,2) obj.price(mm(:,3),1).*mm(:,4)]
             plot(1:size(obj.pu,2),obj.pu,'--rs',... %  1:size(pu,2),obj.cumsumfunc(res),'-m',...
                     1:size(obj.cumsumfunc(x.m),2), obj.cumsumfunc(x.m),'.-k'        );
     %        plot(1:size(pu,2),pu,'--rs',1:size(pu,2),obj.cumsumfunc(m));
               for i=1:size(x.m,1)
                   min_orig=x.m(i,obj.t); 
                   day=floor(min_orig/(24*60)); 
                   hour=floor(rem(min_orig,24*60)/60);                  
                   min=rem(min_orig,60);
                   disp(strcat('May ',num2str(day),',  ',num2str(hour),':',num2str(min),...
                        ' buying ', num2str(x.m(i,obj.n)), ' instances of type ', num2str(x.m(i,obj.type)) ));
               end                            
        end        
    end %methods
    
    methods(Static)
        function testit()
            obj=instance_opt();
            [x score]=obj.find_opt();
            obj.visualize(x);
            disp(strcat('The total cost was ',num2str(score)));
        end
    end %methods static
end %class